package com.music.service;

import com.music.vo.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.SignatureException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * JWT 令牌服务类
 * 负责 Token 的生成、解析、验证和黑名单管理
 */
@Component
public class JwtService {

    // ======================================
    // 常量配置（生产环境建议外部化配置）
    // ======================================

    /**
     * JWT 签名密钥（建议通过配置中心管理）
     */
    private static final String SECRET_KEY = "your-256-bit-secret-key-here-1234567890";

    /**
     * Token 有效期（毫秒）
     */
    private static final long EXPIRATION_TIME = 86400000; // 24小时

    /**
     * Redis 黑名单键前缀
     */
    private static final String BLACKLIST_KEY_PREFIX = "blacklisted_tokens:";

    // ======================================
    // 依赖注入
    // ======================================

    private final RedisTemplate<String, String> redisTemplate;

    /**
     * 构造函数注入 RedisTemplate
     * @param redisTemplate Redis 操作模板
     */
    public JwtService(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // ======================================
    // 核心方法
    // ======================================

    /**
     * 生成 JWT 令牌
     * @param user 用户实体（需包含 username 和 userId）
     * @return 签名的 JWT 字符串
     */
    public String generateToken(User user) {
        return Jwts.builder()
                .setSubject(user.getUsername()) // 主题（通常存用户名）
                .claim("userId", user.getUserId()) // 自定义声明（用户ID）
                .setIssuedAt(new Date()) // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY) // 签名算法和密钥
                .compact();
    }

    /**
     * 从 Token 中解析用户ID
     * @param token JWT 令牌
     * @return 用户ID
     * @throws RuntimeException Token 无效或已过期时抛出异常
     */
    public Integer getUserIdFromToken(String token) {
        // 检查黑名单
        if (isTokenBlacklisted(token)) {
            throw new RuntimeException("令牌已失效，请重新登录");
        }

        // 解析并验证 Token
        Claims claims = parseToken(token);
        return claims.get("userId", Integer.class);
    }

    /**
     * 使指定 Token 失效（加入黑名单）
     * @param token 需要失效的 JWT 令牌
     */
    public void invalidateToken(String token) {
        // 计算剩余有效时间（毫秒）
        long ttl = getTokenRemainingTime(token);

        // 当剩余时间大于0时才存入Redis
        if (ttl > 0) {
            redisTemplate.opsForValue().set(
                    BLACKLIST_KEY_PREFIX + token,
                    "invalidated",
                    ttl,
                    TimeUnit.MILLISECONDS
            );
        }
    }

    // ======================================
    // 辅助方法
    // ======================================

    /**
     * 解析并验证 JWT Token
     * @param token JWT 字符串
     * @return 声明内容
     * @throws RuntimeException 各种验证失败情况
     */
    private Claims parseToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET_KEY)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException ex) {
            throw new RuntimeException("令牌已过期", ex);
        } catch (UnsupportedJwtException ex) {
            throw new RuntimeException("不支持的令牌格式", ex);
        } catch (MalformedJwtException ex) {
            throw new RuntimeException("令牌结构错误", ex);
        } catch (SignatureException ex) {
            throw new RuntimeException("签名验证失败", ex);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException("令牌参数错误", ex);
        }
    }

    /**
     * 检查 Token 是否在黑名单中
     * @param token JWT 字符串
     * @return 是否已被加入黑名单
     */
    private boolean isTokenBlacklisted(String token) {
        Boolean exists = redisTemplate.hasKey(BLACKLIST_KEY_PREFIX + token);
        return exists != null && exists;
    }

    /**
     * 获取 Token 剩余有效时间
     * @param token JWT 字符串
     * @return 剩余时间（毫秒），-1表示无效Token
     */
    private long getTokenRemainingTime(String token) {
        try {
            Claims claims = parseToken(token);
            Date expiration = claims.getExpiration();
            return expiration.getTime() - System.currentTimeMillis();
        } catch (RuntimeException ex) {
            return -1;
        }
    }

    // ======================================
    // 工具方法（可根据需要添加）
    // ======================================

    /**
     * 验证 Token 是否有效（未过期且未在黑名单）
     * @param token JWT 字符串
     * @return 是否有效
     */
    public boolean validateToken(String token) {
        try {
            return !isTokenBlacklisted(token) && getTokenRemainingTime(token) > 0;
        } catch (Exception ex) {
            return false;
        }
    }

    /**
     * 刷新 Token 有效期（需要重新生成）
     * @param token 旧 Token
     * @return 新生成的 Token
     */
    public String refreshToken(String token) {
        Claims claims = parseToken(token);
        User user = new User();
        user.setUsername(claims.getSubject());
        user.setUserId(claims.get("userId", Integer.class));
        invalidateToken(token); // 使旧Token失效
        return generateToken(user);
    }
}